package cn.admin.remoting.impl;

import cn.admin.remoting.*;
import cn.admin.remoting.util.ConnectionUrl;
import cn.admin.remoting.util.UnsafeCast;
import org.apache.mina.common.IoSession;
import org.apache.mina.common.WriteFuture;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class DefaultClient implements Client {

    private static final String SESSION_2_CLIENT = "session2client";
    private String appName;
    private String clientKey;
    private String targetUrl;
    private IoSession session;
    private long respTimeoutMs;
    private byte serializeProtocol;
    private transient volatile Map<Long,ConnectionRequest> pendingRequests;

    DefaultClient(IoSession session,String clientKey) {
        this.session = session;
        this.clientKey = clientKey;
        this.session.setAttribute(SESSION_2_CLIENT,this);
        this.pendingRequests = new ConcurrentHashMap<>();
        if (this.clientKey != null) {
            Object[] tmp = ClientKeyUtil.parseClientKey(this.clientKey);
            this.appName = (String) tmp[0];
            this.targetUrl = (String) tmp[1];
            ConnectionUrl.get(this.targetUrl);
            this.respTimeoutMs = ConnectionUrl.getReadTimeout(this.targetUrl);
            this.serializeProtocol = ConnectionUrl.getSerializeProtocol(this.targetUrl);
        }
    }

    public static Client findBySession(IoSession session) {
        return UnsafeCast.cast(session.getAttribute(SESSION_2_CLIENT));
    }

    @Override
    public int getRemotePort() {
        return this.getConnection().getRemotePort();
    }

    @Override
    public String getConnectionUrl() {
        return this.targetUrl;
    }

    @Override
    public String getAppName() {
        return this.appName;
    }

    @Override
    public String getClientKey() {
        return this.clientKey;
    }

    @Override
    public boolean isConnected() {
        return this.getConnection().isConnected();
    }

    @Override
    public Connection getConnection() {
        return DefaultConnection.findBySession(session);
    }

    @Override
    public String toString() {
        return "TCP客户端连接[" +
                "ClientKey=" + this.clientKey + ", " +
                this.getConnection() + "]";
    }

    @Override
    @Deprecated
    public void destroy() {
        if (this.getConnection() != null) {
            this.getConnection().close();
        }
    }

    @Override
    public void destroy(String cause) {
        if (this.getConnection() != null) {
            this.getConnection().close(cause);
        }
    }

    @Override
    public Object invokeWithSync(Object appRequest, RequestControl control) throws
            RemotingException, InterruptedException {
        ResponseFuture future = this.invokeWithFuture(appRequest,control);
        return future.get();
    }

    @Override
    @Deprecated
    public Object invoke(Object appRequest) throws RemotingException, InterruptedException {

        return this.invokeWithSync(appRequest,null);
    }

    @Override
    @Deprecated
    public Object invoke(Object appRequest, byte protocol) throws RemotingException, InterruptedException {
        return this.invokeWithSync(appRequest,new RequestControlImpl(protocol));
    }

    @Override
    @Deprecated
    public Object invoke(Object appRequest, long timeoutMs) throws RemotingException, InterruptedException {
        return this.invokeWithSync(appRequest,new RequestControlImpl(timeoutMs));
    }

    @Override
    public ResponseFuture invokeWithFuture(Object appRequest, RequestControl control) {
        return this.invokeWithFuture(appRequest,control,null);
    }

    @Override
    @Deprecated
    public ResponseFuture invokeWithFuture(Object appRequest) {
        return this.invokeWithFuture(appRequest,null);
    }

    @Override
    public void invokeWithCallback(Object appRequest, ResponseCallback callback, RequestControl control) {
        this.invokeWithFuture(appRequest,control,callback);
    }

    @Override
    @Deprecated
    public void invoke(Object appRequest, ResponseCallback callback) {
        this.invokeWithCallback(appRequest,callback,null);
    }

    @Override
    @Deprecated
    public void invoke(Object appRequest, ResponseCallback callback, long timeoutMs) {
        this.invokeWithCallback(appRequest,callback,new RequestControlImpl(timeoutMs));
    }

    @Override
    public void oneway(Object appRequest) {
        this.oneway(appRequest,null);
    }

    @Override
    public WriteFuture oneway(Object appRequest, RequestControl control) {
        ConnectionRequest request = control == null ? new ConnectionRequest(appRequest) :
                new ConnectionRequest(appRequest,control.getRspClassLoader());
        request.setDirection((byte) 1);
        request.setSerializeProtocol(this.getProtocol(control));
        return this.getConnection().write(request,null);
    }

    @Override
    public boolean putResponse(ConnectionResponse connectionResponse) {
        return false;
    }

    private ResponseFuture invokeWithFuture(Object appRequest,RequestControl control,
                                            ResponseCallback callback) {
        byte protocol = this.getProtocol(control);
        long timeout = this.getTimeout(control);
        if (timeout <= 0L) {
            throw new IllegalArgumentException("响应超时应该为正值.");
        } else {
            ConnectionRequest request = control == null ? new ConnectionRequest(appRequest) :
                    new ConnectionRequest(appRequest,control.getRspClassLoader());
            DefaultRespFuture respFuture = new DefaultRespFuture(request);
            respFuture.setRespCallback(callback);
            request.setRespTimeout(timeout);
            request.setRespFuture(respFuture);
            request.setSerializeProtocol(serializeProtocol);
            this.getConnection().write(request,
                    ((DefaultRespFuture)request.getRespFuture()).getWriteFutureListener());
            return respFuture;
        }
    }

    private byte getProtocol(RequestControl control) {
        return control != null && control.isProtocolControl() ? control.getProtocol() :
                this.serializeProtocol;
    }

    private long getTimeout(RequestControl control) {
        return control != null && control.isTimeoutControl() ? control.getTimeout() :
                this.respTimeoutMs;
    }

    void addPendingRequest(ConnectionRequest request) {
        this.pendingRequests.put(request.getId(),request);
    }

    public ConnectionRequest getPendingRequest(long requestId) {
        return this.pendingRequests.get(requestId);
    }

    Map<Long,ConnectionRequest> getPendingRequests() {
        return this.pendingRequests;
    }

}
